﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Concurrent;
using System.Configuration;
using PServiceBus.Services.Web;
using PServiceBus.WebSocket.RPC.DTO;
using PServiceBus.Core.Runtime.Extensions;
using PServiceBus.Services.Gateway.Runtime;
using PServiceBus.Core.Runtime;

namespace PServiceBus.WebSocket.RPC.Runtime
{
	public partial class WebSocketMessageBus
	{
		private static readonly ConcurrentDictionary<string, WebSocketMessageBus> _buses =
			new ConcurrentDictionary<string, WebSocketMessageBus>();
		private static readonly ConcurrentDictionary<Guid, WebSocketMessageBus> _socketBuses =
			new ConcurrentDictionary<Guid, WebSocketMessageBus>();
		

		static WebSocketMessageBus() {
			RegisterCommands();
		}

		private static void RegisterCommands() {
			Register(bus => bus.CreateSubscriber);
			Register(bus => bus.ChangeSubscriberDurability);
			Register(bus => bus.Subscribe);
			Register(bus => bus.Update);
			Register(bus => bus.UnSubscribe);
			Register(bus => bus.AddTransport);
			Register(bus => bus.DeleteTransport);
			Register<NameObject, Subscriber>(bus => bus.SelectSubscriber);
			Register<NameObject, Topic>(bus => bus.SelectTopic);
			Register<bool>(bus => bus.Ping);
			Register(bus => bus.DeleteSubscriber);
			Register(bus => bus.DeleteTopic);
			Register(bus => bus.RegisterTopic);
			Register(bus => bus.PublishTopic);
			Register<IEnumerable<Topic>>(bus => bus.GetTopics);
			Register(bus => bus.Disconnect);
		}

		public string UserName { get; set; }

		public string Password { get; set; }

		public string ConnectionID { get; set; }

		public string ClientIpAddress { get; set; }

		public string ClientToken { get; set; }

		internal WebMessageBus MessageBus {
			get {
				return WebMessageBus.Select(ConnectionID, UserName, Password, Headers);
			}
		}

		private static WebSocketMessageBus GetMessageBus(WebSocketRequest command) {
			var connectionID = command.ConnectionID;
			var bus = _buses.GetOrAdd(connectionID, _ =>
			{
				return new WebSocketMessageBus()
				{
					UserName = command.UserName,
					Password = command.Password,
					ConnectionID = command.ConnectionID,
					ClientIpAddress = command.ClientIpAddress,
					ClientToken = command.ClientToken
				};
			});
			return _socketBuses.GetOrAdd(command.SocketID, bus);
		}

		private object ParseCommand(object command, Type type) {
			var json = command.ToJson();
			return json.FromJson<object>(type);
		}

		private Dictionary<string, string> Headers {
			get {
				var headers = new Dictionary<string, string>();
				headers["ConnectionID"] = ConnectionID;
				headers["UserName"] = UserName;
				headers["ClientToken"] = ClientToken;
				headers["ClientIpAddress"] = ClientIpAddress;
				return headers;
			}
		}

		public WebSocketResponse Invoke(WebSocketRequest request) {
			var key = request.Name;
			var response = new WebSocketResponse() { Name = key, CorrelationID = request.CorrelationID };

			var func = _funcs.ContainsKey(key) ? _funcs[key] : null;
			var funcNp = _funcNps.ContainsKey(key) ? _funcNps[key] : null;
			var action = _actions.ContainsKey(key) ? _actions[key] : null;
			var type = _returnTypes.ContainsKey(key) ? _returnTypes[key] : null;

			try {
				if (func != null) response.Result = func(this, ParseCommand(request.Command, type));
				else if (funcNp != null) response.Result = funcNp(this);
				else if (action != null) action(this, ParseCommand(request.Command, type));
			} catch { }

			return response;
		}

		public static WebSocketResponse Execute(WebSocketRequest request) {
			var bus = GetMessageBus(request);
			return bus.Invoke(request);
		}

		public static void Close(Guid socketID) {
			WebSocketMessageBus bus;
			if (_socketBuses.TryRemove(socketID, out bus)){
				bus.MessageBus.Dispose();
				var oldBusKV = _buses.FirstOrDefault(x => x.Value.ConnectionID == bus.ConnectionID);
				var oldBusKey = !String.IsNullOrWhiteSpace(oldBusKV.Key) ? oldBusKV.Key : null;
				_buses.TryRemove(oldBusKey, out bus);
			}
		}
	}
}
